Echarts4r customisation
January 05, 2023
Echarts4r is a nice wrapper around the echarts package for Javascript. It’s easy and does a lot of things for us.
I had a vexing problem in which I basically had a stacked bar chart and upon clicking a particular series, I wanted every other cell except the one clicked to become lighter. This is a behaviour from another visualization that I was trying to mimic.
To do this, we need to write a handler which will work in echarts4r. To give an example
cars |>
e_charts(speed) |>
e_scatter(dist) |>
e_on(
list(seriesName = "dist"),
"function(){alert('Serie clicked')}"
)
This is directly taken from the echarts4r guides.
To understand the guts of the echarts library, I went through the documentation and played around in the console.
I started off using this fiddle
So my primary task could be broken into 3 steps:
- somehow get an object to manipulate the echarts object.
- store all the colors and change only the clicked object color.
- apply the change
The following code does that:
e_on(query = list(),
handler ="
function(params){ //params holds the click data
debugger; // useful for debugging
// create an object pointing to current scope
window['echartsObj'+this._dom.id] = echarts.init(document.querySelector('#' + this._dom.id));
function hexToRgb(hex) { // misc funcs to change between color spaces ,copied from SO
var result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);
return result ? [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16)
] : null;
}
function rgbToHsl(r, g, b){
r /= 255, g /= 255, b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if(max == min){
h = s = 0; // achromatic
}else{
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [(h*360).toFixed(2), s.toFixed(2)*100, l.toFixed(2)*100];
}
function hslToHex(h, s, l) {
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color).toString(16).padStart(2, '0');
};
return `#${f(0)}${f(8)}${f(4)}`;
}
// get option for current object and color too
my2 = window['echartsObj'+this._dom.id].getOption().series
colorss = window['echartsObj'+this._dom.id].getOption().color
my2.map(function (element,index) { // evverything else apart from clicked object gets dimmer
element.data.map(function (data,idx) {
if (params.seriesIndex == index && params.dataIndex== idx) {
data.itemStyle = {
color: colorss[idx]} // keep same color as before
}
else {
var t2 = rgbToHsl.apply(this,hexToRgb(colorss[idx]))
t2 = t2.map(function(d){ return Number(d)})
t2[2] = t2[2] + 25 // reduce luminance by 25 for all except clicked element
data.itemStyle = {
color: hslToHex.apply(this,t2) }
}
})});
window['echartsObj'+this._dom.id].setOption({ // apply new options
series: my2})
}
",
event = "click")
})
This piece of code can be modified to perform other effects by effectively examining the getoption() return object and modifying.